/**
 * @file ictl.c
 * @brief T1 Interrupt Controller Driver
 *
 * @author yemt@witsi.cn
 * @date 2011-08-14
 * @version T1.ICTL.02
 */
#include "config.h"
#include "ictl.h"
#include "arch.h"
#include "iodef.h"
#include "dev.h"

//#define MOD_DEBUG		//..?

#define CP0_COUNT_INT   _BIT(30)        /**< CPO Counter Interrupt Line */
#define ICTL_INT        _BIT(10)        /**< ICTL Line */

static irq_handler_t irq_vector_table[IRQ_MAX]; /**< ICTL Interrupt Vector Table */
static irq_handler_t cp0_handler;       /**< CP0 Counter Interrupt Handler */

#ifdef MOD_DEBUG
static inline const char *cause_excode_text(int cause)
{
	static const char *txt[32] =
	{	"Interrupt",
		"TLB modification",
		"TLB (load or instruction fetch)",
		"TLB (store)",
		"Address error (load or instruction fetch)",
		"Address error (store)",
		"Bus error (instruction fetch)",
		"Bus error (data: load or store)",
		"Syscall",
		"Breakpoint",
		"Reserved instruction",
		"Coprocessor unusable",
		"Arithmetic Overflow",
		"Trap",
		"14",
		"Floating-Point",
		"Implementation-Specific 1(COP2)",
		"CorExtend Unusable",
		"Coprocessor 2",
		"19", "20", "21", "22",
		"Watch Hi/Lo",
		"Machine check",
		"25", "26", "27", "28", "29", "30", "31",
	};
	return txt[(cause & 0x7c) >> 2];
}

#endif	// MOD_DEBUG

#ifdef CONFIG_MMU
extern unsigned long __syscall_entry_start;    /* start address for the .syscall section */
extern unsigned long __syscall_entry_end;    /* end address for the .syscall section */
int syscall_hook(uint32_t epc)
{
	if((epc >= (uint32_t)&__syscall_entry_start)&&(epc < (uint32_t)&__syscall_entry_end))
		return 0;
#ifdef MOD_DEBUG
	printf("The epc[%x] is invalid!\n", epc);
#endif
	return -1;
}
#endif
/**
 * @fn exception_handler
 * @brief Exception Entry
 *
 * @return void
 */
void exception_handler(void)
{
#ifdef MOD_DEBUG
	uint32_t exHandlered = 0;
#endif
        uint32_t tmp;
        tmp = read_c0_cause(); /* CP0 Cause Register */

        if (tmp & CP0_COUNT_INT) {
#ifdef MOD_DEBUG
				//exHandlered = tmp;
#endif
                if (cp0_handler) {
					cp0_handler();
#ifdef MOD_DEBUG
					exHandlered = tmp;
#endif
				} else {
#ifdef MOD_DEBUG
					printf("cp0 TI interrupt handler undefined!\n");
					//printf("cp0 count=%x, compare=%x\n", read_c0_count(), read_c0_compare());
#endif
					write_c0_compare(read_c0_compare());	// write compare to clear TI
				}
        }
        if (tmp & ICTL_INT) {
                uint32_t irqnum = 0;
                irq_handler_t handler;
#ifdef MOD_DEBUG
				//exHandlered = tmp;
#endif
                tmp = ICTL->INT_FS;
                for(irqnum = 0; irqnum < IRQ_MAX; irqnum++){
                        if(((tmp >> irqnum) & 0x01)) {
                                if ((handler = irq_vector_table[irqnum])) {
                                        handler();
#ifdef MOD_DEBUG
									exHandlered = tmp;
								} else {
									printf("interrupt irq[%d]: Handler undefined!\n", irqnum);
#endif
								}
                        }
                }
        }
#ifdef MOD_DEBUG
	if(exHandlered == 0) {
		printf("exception cause code: %s\n", cause_excode_text(tmp));
		printf(" Status: %08x\n", read_c0_status());
		printf("  Cause: %08x\n", tmp);
		printf("    EPC: %08x\n", read_c0_epc());
		printf("BadAddr: %08x\n", read_c0_badvaddr());
	}
#endif
}

/**
 * @fn request_irq
 * @brief Request and Enable an interrupt
 *
 * @param irq_num       Interrupt number
 * @param routine       Interrupt handler
 * @return
 *      -1              Failed
 *      0               Success
 */
int request_irq(irq_no_t irq_num, irq_handler_t routine)
{
        if (irq_num >= IRQ_MAX) return -1;
        if (irq_num == IRQ_CP0_COUNT)
                cp0_handler = routine;
        else
                irq_vector_table[irq_num] = routine;
        irq_enable(irq_num);
        return 0;
}

/**
 * @fn irq_enable
 * @brief Enable an interrupt
 *
 * @param irq_num       Interrupt number
 * @return void
 */
void irq_enable(irq_no_t irq_num)
{
        if (irq_num == IRQ_CP0_COUNT) {
                uint32_t tmp;
                tmp = read_c0_status(); /* CP0 Status Register */
                tmp |= _BIT(15);
                write_c0_status(tmp);
        }
        else
                ICTL->INT_EN |= _BIT(irq_num);
}

/**
 * @fn irq_disable
 * @brief Disable an interrupt
 *
 * @param irq_num       Interrupt number
 * @return void
 */
void irq_disable(irq_no_t irq_num)
{
        if (irq_num == IRQ_CP0_COUNT) {
                uint32_t tmp;
                tmp = read_c0_status(); /* CP0 Status Register */
                tmp &= ~_BIT(15);
                write_c0_status(tmp);
        }
        else
                ICTL->INT_EN &= ~_BIT(irq_num);
}
